package com.hanxiaozhang.aspect;


import com.hanxiaozhang.DistributedLock;
import com.hanxiaozhang.DistributedLockFactory;
import com.hanxiaozhang.annotation.Lock;
import com.hanxiaozhang.constant.LockCategory;
import com.hanxiaozhang.constant.LockTypeEnum;
import com.hanxiaozhang.exception.DistributedLockException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * 〈一句话功能简述〉<br>
 * 〈锁注解切面〉
 *
 * @author hanxinghua
 * @create 2023/7/19
 * @since 1.0.0
 */
@Slf4j
@Aspect
@Component
public class LockAspect {

    @Autowired
    private DistributedLockFactory factory;

    @Pointcut(value = "@annotation(com.hanxiaozhang.annotation.Lock)")
    public void pointcut() {
    }


    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 获取执行方法
        Method method = getMethod(point);
        // 获取锁注解
        Lock lockAnnotation = method.getAnnotation(Lock.class);
        // 获取锁
        DistributedLock lock = getLock(lockAnnotation, point.getArgs(), method);
        // 加锁
        Boolean hasLock = lock(lockAnnotation, lock);
        try {
            // 执行业务代码
            return point.proceed();
        } finally {
            // 解锁
            if (hasLock) {
                lock.unlock();
                log.info("解锁成功，key is [{}]", lock.getLockKey());
            }
        }
    }


    /**
     * 获取执行方法
     *
     * @param point
     * @return
     */
    private Method getMethod(ProceedingJoinPoint point) {
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        return methodSignature.getMethod();
    }


    /**
     * 获取锁
     *
     * @param lockAnnotation
     * @param args
     * @param method
     * @return
     */
    private DistributedLock getLock(Lock lockAnnotation, Object[] args, Method method) {

        DistributedLock lock = null;
        LockCategory lockCategory = lockAnnotation.lockCategory();
        String keyPrefix = lockAnnotation.keyPrefix();
        String key = parseElExpression(args, method, lockAnnotation.keyEl(), String.class);
        if (LockCategory.MYSQL.equals(lockCategory)) {
            lock = factory.getZkLock(keyPrefix, key);
        } else if (LockCategory.REDIS.equals(lockCategory)) {
            lock = factory.getZkLock(keyPrefix + key);
        } else {
            lock = factory.getRedisLock(keyPrefix + key);
        }
        return lock;
    }

    /**
     * 加锁
     *
     * @param lockAnnotation
     * @param lock
     * @return
     * @throws InterruptedException
     */
    private Boolean lock(Lock lockAnnotation, DistributedLock lock) throws InterruptedException {

        Boolean lockFlag = false;
        if (LockTypeEnum.EXCLUSIVE_LOCK.equals(lockAnnotation.lockType())) {
            lockFlag = lock.tryLock();
        } else {
            long waitTime;
            if ((waitTime = lockAnnotation.waitTime()) == -1) {
                lock.lock();
                lockFlag = true;
            } else {
                lockFlag = lock.tryLock(waitTime, lockAnnotation.waitTimeUnit());
            }
        }
        if (!lockFlag) {
            throw new DistributedLockException(lockAnnotation.lockFailedMsg());
        }
        log.info("加锁成功，key is [{}]，type is [{}]，category is [{}]",
                lock.getLockKey(), lockAnnotation.lockType(), lockAnnotation.lockCategory());
        return lockFlag;
    }


    /**
     * 解析EL表达式
     *
     * @param args
     * @param method
     * @param elExpression
     * @param resultType
     * @param <T>
     * @return
     */
    public static <T> T parseElExpression(Object[] args, Method method, String elExpression, Class<T> resultType) {
        Parameter[] parameters = method.getParameters();
        StandardEvaluationContext elContext = new StandardEvaluationContext();
        if (parameters != null && parameters.length > 0) {
            // 设置解析变量
            for (int i = 0; i < parameters.length; i++) {
                String paraName = parameters[i].getName();
                Object paraValue = args[i];
                elContext.setVariable(paraName, paraValue);
            }
        }
        ExpressionParser parser = new SpelExpressionParser();
        return parser.parseExpression(elExpression)
                .getValue(elContext, resultType);
    }

}
